#pragma once

#include <AP_HAL/AP_HAL_Boards.h>

#if AP_SIM_ENABLED

#include <AP_Math/AP_Math.h>
#include <GCS_MAVLink/GCS_MAVLink.h>
#include <AP_Baro/AP_Baro.h>
#include <AP_Airspeed/AP_Airspeed.h>
#include <AP_Common/Location.h>
#include <AP_Compass/AP_Compass.h>
#include <AP_InertialSensor/AP_InertialSensor.h>

#include "SIM_Buzzer.h"
#include "SIM_Gripper_EPM.h"
#include "SIM_Gripper_Servo.h"
#include "SIM_I2C.h"
#include "SIM_SPI.h"
#include "SIM_Parachute.h"
#include "SIM_Precland.h"
#include "SIM_Sprayer.h"
#include "SIM_ToneAlarm.h"
#include "SIM_EFI_MegaSquirt.h"
#include "SIM_RichenPower.h"
#include "SIM_Loweheiser.h"
#include "SIM_FETtecOneWireESC.h"
#include "SIM_IntelligentEnergy24.h"
#include "SIM_Ship.h"
#include "SIM_SlungPayload.h"
#include "SIM_Tether.h"
#include "SIM_GPS.h"
#include "SIM_DroneCANDevice.h"
#include "SIM_ADSB_Sagetech_MXS.h"
#include "SIM_Volz.h"
#include "SIM_AIS.h"

namespace SITL {

enum class LedLayout {
    ROWS=0,
    LUMINOUSBEE=1,
};
    
struct vector3f_array {
    uint16_t length;
    Vector3f *data;
};

struct float_array {
    uint16_t length;
    float *data;
};

class StratoBlimp;
class Glider;
class FlightAxis;

struct sitl_fdm {
    // this is the structure passed between FDM models and the main SITL code
    uint64_t timestamp_us;
    Location home;
    double latitude, longitude; // degrees
    double altitude;  // MSL
    double heading;   // degrees
    double speedN, speedE, speedD; // m/s
    double xAccel, yAccel, zAccel;       // m/s/s in body frame
    double rollRate, pitchRate, yawRate; // degrees/s in body frame
    double rollDeg, pitchDeg, yawDeg;    // euler angles, degrees
    Quaternion quaternion;
    double airspeed; // m/s, EAS
    Vector3f velocity_air_bf; // velocity relative to airmass, body frame, TAS
    double battery_voltage; // Volts
    double battery_current; // Amps
    double battery_remaining; // Ah, if non-zero capacity
    uint8_t num_motors;
    uint32_t motor_mask;
    float rpm[32];         // RPM of all motors
    uint8_t rcin_chan_count;
    float  rcin[12];         // RC input 0..1
    double range;           // rangefinder value
    Vector3f bodyMagField;  // Truth XYZ magnetic field vector in body-frame. Includes motor interference. Units are milli-Gauss.
    Vector3f angAccel; // Angular acceleration in degrees/s/s about the XYZ body axes

    struct {
        // data from simulated laser scanner, if available
        struct vector3f_array points;
        struct float_array ranges;
    } scanner;

    #define SITL_NUM_RANGEFINDERS 10
    float rangefinder_m[SITL_NUM_RANGEFINDERS];
    float airspeed_raw_pressure[AIRSPEED_MAX_SENSORS];

    struct {
        float speed;
        float direction;
    } wind_vane_apparent;

    bool is_lock_step_scheduled;

    // earthframe wind, from backends that know it
    Vector3f wind_ef;

    // AGL altitude, usually derived from the terrain database in simulation:
    float height_agl;

};

// number of rc output channels
#define SITL_NUM_CHANNELS 32

class SIM {
public:

    SIM() {
        AP_Param::setup_object_defaults(this, var_info);
        AP_Param::setup_object_defaults(this, var_info2);
        AP_Param::setup_object_defaults(this, var_info3);
#if AP_SIM_GPS_ENABLED
        AP_Param::setup_object_defaults(this, var_gps);
#endif
        AP_Param::setup_object_defaults(this, var_mag);
        AP_Param::setup_object_defaults(this, var_ins);
#ifdef SFML_JOYSTICK
        AP_Param::setup_object_defaults(this, var_sfml_joystick);
#endif // SFML_JOYSTICK
        for (uint8_t i=0; i<BARO_MAX_INSTANCES; i++) {
            AP_Param::setup_object_defaults(&baro[i], baro[i].var_info);
        }
        for (uint8_t i=0; i<AIRSPEED_MAX_SENSORS; i++) {
            AP_Param::setup_object_defaults(&airspeed[i], airspeed[i].var_info);
        }
        // set compass offset
        for (uint8_t i = 0; i < HAL_COMPASS_MAX_SENSORS; i++) {
            mag_ofs[i].set(Vector3f(5, 13, -18));
        }
        if (_singleton != nullptr) {
            AP_HAL::panic("Too many SITL instances");
        }
        _singleton = this;
    }

    /* Do not allow copies */
    CLASS_NO_COPY(SIM);

    static SIM *_singleton;
    static SIM *get_singleton() { return _singleton; }

    enum SITL_RCFail {
        SITL_RCFail_None = 0,
        SITL_RCFail_NoPulses = 1,
        SITL_RCFail_Throttle950 = 2,
        SITL_RCFail_Protocol_Fail_Bit_Set = 3,
    };

    enum GPSHeading {
        GPS_HEADING_NONE = 0,
        GPS_HEADING_HDT  = 1,
        GPS_HEADING_THS  = 2,
        GPS_HEADING_KSXT = 3,
        GPS_HEADING_BASE = 4,  // act as an RTK base
    };

    struct sitl_fdm state;

    // throttle when motors are active
    float throttle;

    static const struct AP_Param::GroupInfo var_info[];
    static const struct AP_Param::GroupInfo var_info2[];
    static const struct AP_Param::GroupInfo var_info3[];
#if AP_SIM_GPS_ENABLED
    static const struct AP_Param::GroupInfo var_gps[];
#endif
    static const struct AP_Param::GroupInfo var_mag[];
    static const struct AP_Param::GroupInfo var_ins[];
#ifdef SFML_JOYSTICK
    static const struct AP_Param::GroupInfo var_sfml_joystick[];
#endif //SFML_JOYSTICK

    // Board Orientation (and inverse)
    Matrix3f ahrs_rotation;
    Matrix3f ahrs_rotation_inv;

    AP_Float mag_noise;   // in mag units (earth field is 818)
    AP_Vector3f mag_mot;  // in mag units per amp
    AP_Vector3f mag_ofs[HAL_COMPASS_MAX_SENSORS];  // in mag units
    AP_Vector3f mag_diag[HAL_COMPASS_MAX_SENSORS];  // diagonal corrections
    AP_Vector3f mag_offdiag[HAL_COMPASS_MAX_SENSORS];  // off-diagonal corrections
    AP_Int8 mag_orient[HAL_COMPASS_MAX_SENSORS];   // external compass orientation
    AP_Int8 mag_fail[HAL_COMPASS_MAX_SENSORS];   // fail magnetometer, 1 for no data, 2 for freeze
    AP_Int8 mag_save_ids;

    AP_Float sonar_glitch;// probability between 0-1 that any given sonar sample will read as max distance
    AP_Float sonar_noise; // in metres
    AP_Float sonar_scale; // meters per volt
    AP_Int8 sonar_rot;  // from rotations enumeration

    AP_Float drift_speed; // degrees/second/minute
    AP_Float drift_time;  // period in minutes
    AP_Float engine_mul;  // engine multiplier
    AP_Int32 engine_fail; // mask of engine/motor servo outputs to fail

    // initial offset on GPS lat/lon, used to shift origin
    AP_Float gps_init_lat_ofs;
    AP_Float gps_init_lon_ofs;
    AP_Float gps_init_alt_ofs;

    // log number for GPS::update_file()
    AP_Int16 gps_log_num;

    AP_Float batt_voltage; // battery voltage base
    AP_Float batt_capacity_ah; // battery capacity in Ah
    AP_Int8  rc_fail;     // fail RC input
    AP_Int8  rc_chancount; // channel count
    AP_Int8  float_exception; // enable floating point exception checks
    AP_Int32 can_servo_mask; // mask of servos/escs coming from CAN

#if HAL_NUM_CAN_IFACES
    enum class CANTransport : uint8_t {
      None = 0,
      MulticastUDP = 1,
      SocketCAN = 2,
    };
    AP_Enum<CANTransport> can_transport[HAL_NUM_CAN_IFACES];
#endif

    AP_Int8  flow_enable; // enable simulated optflow
    AP_Int16 flow_rate; // optflow data rate (Hz)
    AP_Int8  flow_delay; // optflow data delay
    AP_Int8  terrain_enable; // enable using terrain for height
    AP_Int16 pin_mask; // for GPIO emulation
    AP_Float speedup; // simulation speedup
    AP_Int8  odom_enable; // enable visual odometry data
    AP_Int8  telem_baudlimit_enable; // enable baudrate limiting on links
    AP_Float flow_noise; // optical flow measurement noise (rad/sec)
    AP_Int8  baro_count; // number of simulated baros to create
    AP_Int8  imu_count; // number of simulated IMUs to create
    AP_Int32 loop_delay; // extra delay to add to every loop
    AP_Float mag_scaling[MAX_CONNECTED_MAGS]; // scaling factor
    AP_Int32 mag_devid[MAX_CONNECTED_MAGS]; // Mag devid
    AP_Float buoyancy; // submarine buoyancy in Newtons
    AP_Int16 loop_rate_hz;
    AP_Int16 loop_time_jitter_us;
    AP_Int32 on_hardware_output_enable_mask;  // mask of output channels passed through to actual hardware
    AP_Int16 on_hardware_relay_enable_mask;   // mask of relays passed through to actual hardware

    AP_Float uart_byte_loss_pct;

#ifdef SFML_JOYSTICK
    AP_Int8 sfml_joystick_id;
    AP_Int8 sfml_joystick_axis[8];
#endif

    // baro parameters
    class BaroParm {
    public:
        static const struct AP_Param::GroupInfo var_info[];
        AP_Float noise;  // in metres
        AP_Float drift;  // in metres per second
        AP_Float glitch; // glitch in meters
        AP_Int8  freeze; // freeze baro to last recorded altitude
        AP_Int8  disable; // disable simulated barometers
        AP_Int16 delay;  // barometer data delay in ms

        // wind coefficients
        AP_Float wcof_xp;
        AP_Float wcof_xn;
        AP_Float wcof_yp;
        AP_Float wcof_yn;
        AP_Float wcof_zp;
        AP_Float wcof_zn;
    };
    BaroParm baro[BARO_MAX_INSTANCES];

    // airspeed parameters
    class AirspeedParm {
    public:
        static const struct AP_Param::GroupInfo var_info[];
        AP_Float noise;  // pressure noise
        AP_Float fail;   // airspeed value in m/s to fail to
        AP_Float fail_pressure; // pitot tube failure pressure in Pa
        AP_Float fail_pitot_pressure; // pitot tube failure pressure in Pa
        AP_Float offset; // airspeed sensor offset in m/s
        AP_Float ratio; // airspeed ratios
        AP_Int8  signflip;
    };
    AirspeedParm airspeed[AIRSPEED_MAX_SENSORS];

    class ServoParams {
    public:
        ServoParams(void) {
            AP_Param::setup_object_defaults(this, var_info);
        }
        static const struct AP_Param::GroupInfo var_info[];
        AP_Float servo_speed; // servo speed in seconds per 60 degrees
        AP_Float servo_delay; // servo delay in seconds
        AP_Float servo_filter; // servo 2p filter in Hz
    };
    ServoParams servo;

    class GPSParms {
    public:
        GPSParms(void) {
            AP_Param::setup_object_defaults(this, var_info);
        }
        static const struct AP_Param::GroupInfo var_info[];

        AP_Float noise; // amplitude of the gps altitude error
        AP_Int16 lock_time; // delay in seconds before GPS gets lock
        AP_Int16 alt_offset; // gps alt error
        AP_Int8  enabled; // enable simulated GPS
        AP_Int16 delay_ms;   // delay in milliseconds
        AP_Int8  type; // see enum SITL::GPS::Type
        AP_Float byteloss;// byte loss as a percent
        AP_Int8  numsats; // number of visible satellites
        AP_Vector3f glitch;  // glitch offsets in lat, lon and altitude
        AP_Int8  hertz;   // GPS update rate in Hz
        AP_Int8 hdg_enabled; // enable the output of a NMEA heading HDT sentence or UBLOX RELPOSNED
        AP_Float drift_alt; // altitude drift error
        AP_Vector3f pos_offset;  // XYZ position of the GPS antenna phase centre relative to the body frame origin (m)
        AP_Float accuracy;
        AP_Vector3f vel_err; // Velocity error offsets in NED (x = N, y = E, z = D)
        AP_Int8 jam; // jamming simulation enable
        AP_Float heading_offset; // heading offset in degrees
    };
    GPSParms gps[AP_SIM_MAX_GPS_SENSORS];

#if AP_SIM_VICON_ENABLED
    class ViconParms {
    public:
        ViconParms(void) {
            AP_Param::setup_object_defaults(this, var_info);
        }
        static const struct AP_Param::GroupInfo var_info[];

        // vicon parameters
        AP_Vector3f pos_offset;   // XYZ position of the vicon sensor relative to the body frame origin (m)
        AP_Vector3f glitch;   // glitch in meters in vicon's local NED frame
        AP_Float pos_stddev;       // noise in meters in vicon's local NED frame
        AP_Float vel_stddev;       // noise in m/s in vicon's local NED frame
        AP_Int8 fail;         // trigger vicon failure
        AP_Int16 yaw;         // vicon local yaw in degrees
        AP_Int16 yaw_error;   // vicon yaw error in degrees (added to reported yaw sent to vehicle)
        AP_Int8 type_mask;    // vicon message type mask (bit0:vision position estimate, bit1:vision speed estimate, bit2:vicon position estimate)
        AP_Vector3f vel_glitch;   // velocity glitch in m/s in vicon's local frame
        AP_Int16 rate_hz;     // vicon data rate in Hz
    };
    ViconParms vicon;
#endif  // AP_SIM_VICON_ENABLED

    // physics model parameters
    class ModelParm {
    public:
        static const struct AP_Param::GroupInfo var_info[];
#if AP_SIM_STRATOBLIMP_ENABLED
        StratoBlimp *stratoblimp_ptr;
#endif
#if AP_SIM_SHIP_ENABLED
        ShipSim shipsim;
#endif
#if AP_SIM_GLIDER_ENABLED
        Glider *glider_ptr;
#endif
#if AP_SIM_SLUNGPAYLOAD_ENABLED
        SlungPayloadSim slung_payload_sim;
#endif
#if AP_SIM_TETHER_ENABLED
        TetherSim tether_sim;
#endif
#if AP_SIM_FLIGHTAXIS_ENABLED
        FlightAxis *flightaxis_ptr;
#endif
#if AP_SIM_AIS_ENABLED
        class AIS *ais_ptr;
#endif  // AP_SIM_AIS_ENABLED
    };
    ModelParm models;
    
    // EFI type
    enum EFIType {
        EFI_TYPE_NONE = 0,
        EFI_TYPE_MS = 1,
        EFI_TYPE_LOWEHEISER = 2,
        EFI_TYPE_HIRTH = 8,
    };
    
    AP_Int8  efi_type;

    // wind control
    enum WindType {
        WIND_TYPE_SQRT = 0,
        WIND_TYPE_NO_LIMIT = 1,
        WIND_TYPE_COEF = 2,
    };
    
    float wind_speed_active;
    float wind_direction_active;
    float wind_dir_z_active;
    AP_Float wind_speed;
    AP_Float wind_direction;
    AP_Float wind_turbulance;
    AP_Float wind_dir_z;
    AP_Float wind_change_tc;
    AP_Int8  wind_type; // enum WindLimitType
    AP_Float wind_type_alt;
    AP_Float wind_type_coef;

    AP_Int16  mag_delay; // magnetometer data delay in ms

    // ADSB related run-time options
    enum class ADSBType {
        Shortcut = 0,
        SageTechMXS = 3,
    };
    AP_Enum<ADSBType> adsb_types;  // bitmask of active ADSB types
    AP_Int16 adsb_plane_count;
    AP_Float adsb_radius_m;
    AP_Float adsb_altitude_m;
    AP_Int8  adsb_tx;

    // Earth magnetic field anomaly
    AP_Vector3f mag_anomaly_ned; // NED anomaly vector at ground level (mGauss)
    AP_Float mag_anomaly_hgt; // height above ground where anomally strength has decayed to 1/8 of the ground level value (m)

    // Body frame sensor position offsets
    AP_Vector3f imu_pos_offset;     // XYZ position of the IMU accelerometer relative to the body frame origin (m)
    AP_Vector3f rngfnd_pos_offset;  // XYZ position of the range finder zero range datum relative to the body frame origin (m)
    AP_Vector3f optflow_pos_offset; // XYZ position of the optical flow sensor focal point relative to the body frame origin (m)

    // barometer temperature control
    AP_Float temp_start;            // [deg C] Barometer start temperature
    AP_Float temp_board_offset;     // [deg C] Barometer board temperature offset from atmospheric temperature
    AP_Float temp_tconst;           // [deg C] Barometer warmup temperature time constant
    AP_Float temp_baro_factor;
    
    AP_Int8 thermal_scenario;

    // weight on wheels pin
    AP_Int8 wow_pin;

    // vibration frequencies in Hz on each axis
    AP_Vector3f vibe_freq;

    // max frequency to use as baseline for adding motor noise for the gyros and accels
    AP_Float vibe_motor;
    // amplitude scaling of motor noise relative to gyro/accel noise
    AP_Float vibe_motor_scale;

    // what harmonics to generate
    AP_Int16 vibe_motor_harmonics;

    // what servos are motors
    AP_Int32 vibe_motor_mask;
    
    // minimum throttle for addition of ins noise
    AP_Float ins_noise_throttle_min;

    struct {
        AP_Float x;
        AP_Float y;
        AP_Float z;
        AP_Int32 t;

        uint32_t start_ms;
    } shove;

    struct {
        AP_Float x;
        AP_Float y;
        AP_Float z;
        AP_Int32 t;

        uint32_t start_ms;
    } twist;

    AP_Int8 gnd_behav;

    AP_Enum<Rotation> imu_orientation;

    struct {
        AP_Int8 enable;     // 0: disabled, 1: roll and pitch, 2: roll, pitch and heave
        AP_Float length;    // m
        AP_Float amp;       // m
        AP_Float direction; // deg (direction wave is coming from)
        AP_Float speed;     // m/s
    } wave;

    struct {
        AP_Float direction; // deg (direction tide is coming from)
        AP_Float speed;     // m/s
    } tide;

    // original simulated position
    struct {
        AP_Float lat;
        AP_Float lng;
        AP_Float alt; // metres
        AP_Float hdg; // 0 to 360
    } opos;

    uint16_t irlock_port;
    uint16_t rcin_port;

    time_t start_time_UTC;

    void simstate_send(mavlink_channel_t chan) const;
    void sim_state_send(mavlink_channel_t chan) const;

    void Log_Write_SIMSTATE();

    // convert a set of roll rates from earth frame to body frame
    static void convert_body_frame(double rollDeg, double pitchDeg,
                                   double rollRate, double pitchRate, double yawRate,
                                   double *p, double *q, double *r);

    // convert a set of roll rates from body frame to earth frame
    static Vector3f convert_earth_frame(const Matrix3f &dcm, const Vector3f &gyro);

    int i2c_ioctl(uint8_t i2c_operation, void *data) {
        return i2c_sim.ioctl(i2c_operation, data);
    }

    int spi_ioctl(uint8_t bus, uint8_t cs_pin, uint8_t spi_operation, void *data) {
        return spi_sim.ioctl(bus, cs_pin, spi_operation, data);
    }

    Sprayer sprayer_sim;

    Gripper_Servo gripper_sim;
    Gripper_EPM gripper_epm_sim;

    Parachute parachute_sim;
    Buzzer buzzer_sim;
    I2C i2c_sim;
    SPI spi_sim;
    ToneAlarm tonealarm_sim;
    SIM_Precland precland_sim;
    RichenPower richenpower_sim;
#if AP_SIM_LOWEHEISER_ENABLED
    Loweheiser loweheiser_sim;
#endif
    IntelligentEnergy24 ie24_sim;
    FETtecOneWireESC fetteconewireesc_sim;
#if AP_SIM_VOLZ_ENABLED
    Volz volz_sim;
#endif  // AP_SIM_VOLZ_ENABLED
#if AP_TEST_DRONECAN_DRIVERS
    DroneCANDevice dronecan_sim;
#endif

    // ESC telemetry
    AP_Int8 esc_telem;
    // RPM when motors are armed
    AP_Float esc_rpm_armed;

    struct {
        // LED state, for serial LED emulation
        struct {
            uint8_t rgb[3];
        } rgb[16][32];
        uint8_t num_leds[16];
        uint32_t send_counter;
    } led;

    AP_Int8 led_layout;

    // get the rangefinder reading for the desired instance, returns -1 for no data
    float get_rangefinder(uint8_t instance);

    float measure_distance_at_angle_bf(const Location &location, float angle) const;

    // get the apparent wind speed and direction as set by external physics backend
    float get_apparent_wind_dir() const{return state.wind_vane_apparent.direction;}
    float get_apparent_wind_spd() const{return state.wind_vane_apparent.speed;}

#if HAL_INS_TEMPERATURE_CAL_ENABLE
    // IMU temperature calibration params
    AP_Float imu_temp_start;
    AP_Float imu_temp_end;
    AP_Float imu_temp_tconst;
    AP_Float imu_temp_fixed;
    AP_InertialSensor_TCal imu_tcal[INS_MAX_INSTANCES];
#endif

    // IMU control parameters
    AP_Float gyro_noise[INS_MAX_INSTANCES];  // in degrees/second
    AP_Vector3f gyro_scale[INS_MAX_INSTANCES];  // percentage
    AP_Vector3f gyro_bias[INS_MAX_INSTANCES]; // in rad/s
    AP_Float accel_noise[INS_MAX_INSTANCES]; // in m/s/s
    AP_Vector3f accel_bias[INS_MAX_INSTANCES]; // in m/s/s
    AP_Vector3f accel_scale[INS_MAX_INSTANCES]; // in m/s/s
    AP_Vector3f accel_trim;
    AP_Float accel_fail[INS_MAX_INSTANCES];  // accelerometer failure value
    // gyro and accel fail masks
    AP_Int8 gyro_fail_mask;
    AP_Int8 accel_fail_mask;

    // Sailboat sim only
    AP_Int8 sail_type;

    // Master instance to use servos from with slave instances
    AP_Int8 ride_along_master;

    // clamp simulation - servo channel starting at offset 1 (usually ailerons)
    AP_Int8 clamp_ch;

#if AP_SIM_INS_FILE_ENABLED
    enum INSFileMode {
        INS_FILE_NONE = 0,
        INS_FILE_READ = 1,
        INS_FILE_WRITE = 2,
        INS_FILE_READ_STOP_ON_EOF = 3,
    };
    AP_Int8 gyro_file_rw;
    AP_Int8 accel_file_rw;
#endif

#ifdef WITH_SITL_OSD
    AP_Int16 osd_rows;
    AP_Int16 osd_columns;
#endif

    // Allow inhibiting of SITL only sim state messages over MAVLink
    // This gives more realistic data rates for testing links
    void set_stop_MAVLink_sim_state() { stop_MAVLink_sim_state = true; }
    bool stop_MAVLink_sim_state;

    /*
      used by scripting to control simulated aircraft position
     */
    bool set_pose(uint8_t instance, const Location &loc, const Quaternion &quat,
                  const Vector3f &velocity_ef, const Vector3f &gyro_rads);
};

} // namespace SITL


namespace AP {
    SITL::SIM *sitl();
};

#endif // AP_SIM_ENABLED
